{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 什么是正则表达式？\n",
    "\n",
    "> 正则表达式是一组由字母和符号组成的特殊文本，它可以用来从文本中找出满足你想要的格式的句子。\n",
    "\n",
    "一个正则表达式是一种从左到右匹配主体字符串的模式。 “Regular expression”这个词比较拗口，我们常使用缩写的术语“regex”或“regexp”。 正则表达式可以从一个基础字符串中根据一定的匹配模式替换文本中的字符串、验证表单、提取字符串等等。\n",
    "\n",
    "想象你正在写一个应用，然后你想设定一个用户命名的规则，让用户名包含字符、数字、下划线和连字符，以及限制字符的个数，好让名字看起来没那么丑。 我们使用以下正则表达式来验证一个用户名：\n",
    "\n",
    "![](https://pptwinpics.oss-cn-beijing.aliyuncs.com/regexp-cn_20200225142334.png)\n",
    "\n",
    "以上的正则表达式可以接受 `john_doe`、`jo-hn_doe`、`john12_as`。 但不匹配`Jo`，因为它包含了大写的字母而且太短了。\n",
    "\n",
    "让我们用 Python 来实现一下。\n",
    "\n",
    "# Python 正则表达式\n",
    "\n",
    "正则表达式是一个特殊的字符序列，它能帮助你方便的检查一个字符串是否与某种模式匹配。\n",
    "\n",
    "Python 自1.5版本起增加了re 模块，它提供 Perl 风格的正则表达式模式。\n",
    "\n",
    "re 模块使 Python 语言拥有全部的正则表达式功能。\n",
    "\n",
    "compile 函数根据一个模式字符串和可选的标志参数生成一个正则表达式对象。该对象拥有一系列方法用于正则表达式匹配和替换。\n",
    "\n",
    "re 模块也提供了与这些方法功能完全一致的函数，这些函数使用一个模式字符串做为它们的第一个参数。\n",
    "\n",
    "本章节主要介绍Python中常用的正则表达式处理函数。\n",
    "\n",
    "## re.match 方法\n",
    "\n",
    "re.match 尝试从字符串的起始位置匹配一个模式，如果不是起始位置匹配成功的话，match()就返回none。\n",
    "\n",
    "函数语法：\n",
    "\n",
    "```\n",
    "re.match(pattern, string, flags=0)\n",
    "```\n",
    "\n",
    "函数参数说明：\n",
    "\n",
    "\n",
    "|参数|\t描述|\n",
    "|----|---|\n",
    "|pattern|\t匹配的正则表达式|\n",
    "|string|\t要匹配的字符串。|\n",
    "|flags|\t标志位，用于控制正则表达式的匹配方式，如：是否区分大小写，多行匹配等等。|\n",
    "\n",
    "参见：[正则表达式修饰符 - 可选标志]()\n",
    "\n",
    "匹配成功`re.match`方法返回一个匹配的对象，否则返回`None`。\n",
    "\n",
    "我们可以使用`group(num)` 或 `groups()` 匹配对象函数来获取匹配表达式。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(0, 3)\n",
      "None\n"
     ]
    }
   ],
   "source": [
    "#!/usr/bin/python\n",
    "# -*- coding: UTF-8 -*- \n",
    " \n",
    "import re\n",
    "print(re.match('www', 'www.runoob.com').span())  # 在起始位置匹配\n",
    "print(re.match('com', 'www.runoob.com'))         # 不在起始位置匹配"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "hello\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "# re.match 返回一个Match Object 对象\n",
    "# 对象提供了 group() 方法，来获取匹配的结果\n",
    "result = re.match(\"hello\",\"hello,world\")\n",
    "if result:\n",
    "    print(result.group())\n",
    "else:\n",
    "    print(\"匹配失败!\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "umji\n",
      "0\n",
      "4\n",
      "(0, 4)\n",
      "<class 'str'>\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "a = r\"umji\"\n",
    "match = re.search(a,'umji isbest umji inworld')\n",
    "if match:\n",
    "    print(match.group())\n",
    "    print(match.start())\n",
    "    print(match.end())\n",
    "    print(match.span())\n",
    "    print(type(match.group()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "|方法名称|\t作用|\n",
    "|-------|----|\n",
    "|group|\t以str形式返回对象中match的元素|\n",
    "|start|\t返回开始位置|\n",
    "|end|\t返回结束位置|\n",
    "|span|\t以tuple形式返回范围|\n",
    "\n",
    "## re.search 方法\n",
    "\n",
    "`re.search()`函数会在字符串内查找模式匹配,只要找到第一个匹配然后返回，如果字符串没有匹配，则返回None。\n",
    "\n",
    "```\n",
    "re.search(pattern, string, flags=0)\n",
    "```\n",
    "\n",
    "要求：匹配出文章阅读的次数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "9999\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "\n",
    "ret = re.search(r\"\\d+\", \"阅读次数为 9999\")\n",
    "print(ret.group())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## match()和search()的区别：\n",
    "\n",
    "- `match()`函数只检测RE是不是在string的开始位置匹配，\n",
    "- `search()`会扫描整个string查找匹配\n",
    "- `match()`只有在0位置匹配成功的话才有返回，如果不是开始位置匹配成功的话，match()就返回none\n",
    "\n",
    "举例说明："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(0, 5)\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "print(re.match('super', 'superstition').span())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "None\n"
     ]
    }
   ],
   "source": [
    "print(re.match('super','insuperable'))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(0, 5)\n"
     ]
    }
   ],
   "source": [
    "print(re.search('super','superstition').span())\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(2, 7)\n"
     ]
    }
   ],
   "source": [
    "print(re.search('super','insuperable').span())\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## re.sub 方法（检索和替换）\n",
    "Python 的 re 模块提供了re.sub用于替换字符串中的匹配项。\n",
    "\n",
    "语法：\n",
    "```\n",
    "re.sub(pattern, repl, string, count=0, flags=0)\n",
    "\n",
    "```\n",
    "\n",
    "参数：\n",
    "\n",
    "- pattern : 正则中的模式字符串。\n",
    "- repl : 替换的字符串，也可为一个函数。\n",
    "- string : 要被查找替换的原始字符串。\n",
    "- count : 模式匹配后替换的最大次数，默认 0 表示替换所有的匹配。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "电话号码是:  2004-959-559 \n",
      "电话号码是 :  2004959559\n"
     ]
    }
   ],
   "source": [
    "#!/usr/bin/python\n",
    "# -*- coding: UTF-8 -*-\n",
    " \n",
    "import re\n",
    " \n",
    "phone = \"2004-959-559 # 这是一个国外电话号码\"\n",
    " \n",
    "# 删除字符串中的 Python注释 \n",
    "num = re.sub(r'#.*$', \"\", phone)\n",
    "print(\"电话号码是: \", num)\n",
    " \n",
    "# 删除非数字(-)的字符串 \n",
    "num = re.sub(r'\\D', \"\", phone)\n",
    "print(\"电话号码是 : \", num)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### repl 参数也可以是一个函数\n",
    "\n",
    "以下实例中将字符串中的匹配的数字乘以 2："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "A46G8HFD1134\n"
     ]
    }
   ],
   "source": [
    "#!/usr/bin/python\n",
    "# -*- coding: UTF-8 -*-\n",
    " \n",
    "import re\n",
    " \n",
    "# 将匹配的数字乘以 2\n",
    "def double(matched):\n",
    "    value = int(matched.group('value'))\n",
    "    return str(value * 2)\n",
    " \n",
    "s = 'A23G4HFD567'\n",
    "print(re.sub('(?P<value>\\d+)', double, s))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## re.compile 方法\n",
    "compile 函数用于编译正则表达式，生成一个正则表达式（ Pattern ）对象，供 match() 和 search() 这两个函数使用。\n",
    "\n",
    "语法格式为：\n",
    "\n",
    "```\n",
    "re.compile(pattern[, flags])\n",
    "```\n",
    "\n",
    "参数：\n",
    "\n",
    "- pattern : 一个字符串形式的正则表达式\n",
    "\n",
    "- flags : 可选，表示匹配模式，比如忽略大小写，多行模式等，具体参数为：\n",
    "\n",
    "    - re.I 忽略大小写\n",
    "    - re.L 表示特殊字符集 \\w, \\W, \\b, \\B, \\s, \\S 依赖于当前环境\n",
    "    - re.M 多行模式\n",
    "    - re.S 即为 . 并且包括换行符在内的任意字符（. 不包括换行符）\n",
    "    - re.U 表示特殊字符集 \\w, \\W, \\b, \\B, \\d, \\D, \\s, \\S 依赖于 Unicode 字符属性数据库\n",
    "    - re.X 为了增加可读性，忽略空格和 # 后面的注释\n",
    "实例"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "None\n",
      "None\n",
      "<re.Match object; span=(3, 5), match='12'>\n",
      "12\n",
      "3\n",
      "5\n",
      "(3, 5)\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "pattern = re.compile(r'\\d+')                    # 用于匹配至少一个数字\n",
    "m = pattern.match('one12twothree34four')        # 查找头部，没有匹配\n",
    "print(m)\n",
    "\n",
    "m = pattern.match('one12twothree34four', 2, 10) # 从'e'的位置开始匹配，没有匹配\n",
    "print(m)\n",
    "\n",
    "m = pattern.match('one12twothree34four', 3, 10) # 从'1'的位置开始匹配，正好匹配\n",
    "print(m)\n",
    "\n",
    "print(m.group(0))   # 可省略 0\n",
    "print(m.start(0))   # 可省略 0\n",
    "print(m.end(0))\n",
    "print(m.span(0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在上面，当匹配成功时返回一个 Match 对象，其中：\n",
    "\n",
    "- `group([group1, …])` 方法用于获得一个或多个分组匹配的字符串，当要获得整个匹配的子串时，可直接使用 group() 或 group(0)；\n",
    "- `start([group])` 方法用于获取分组匹配的子串在整个字符串中的起始位置（子串第一个字符的索引），参数默认值为 0；\n",
    "- `end([group])` 方法用于获取分组匹配的子串在整个字符串中的结束位置（子串最后一个字符的索引+1），参数默认值为 0；\n",
    "- `span([group])` 方法返回 (start(group), end(group))。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<re.Match object; span=(0, 11), match='Hello World'>\n",
      "Hello World\n",
      "(0, 11)\n",
      "Hello\n",
      "(0, 5)\n",
      "World\n",
      "(6, 11)\n",
      "('Hello', 'World')\n"
     ]
    },
    {
     "ename": "IndexError",
     "evalue": "no such group",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mIndexError\u001b[0m                                Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-28-54d224340ffc>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m     10\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mm\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mspan\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m)\u001b[0m                            \u001b[0;31m# 返回第二个分组匹配成功的子串\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     11\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mm\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgroups\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m                            \u001b[0;31m# 等价于 (m.group(1), m.group(2), ...)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 12\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mm\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgroup\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m3\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m                            \u001b[0;31m# 不存在第三个分组\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;31mIndexError\u001b[0m: no such group"
     ]
    }
   ],
   "source": [
    "import re\n",
    "pattern = re.compile(r'([a-z]+) ([a-z]+)', re.I)   # re.I 表示忽略大小写\n",
    "m = pattern.match('Hello World Wide Web')\n",
    "print(m)                               # 匹配成功，返回一个 Match 对象\n",
    "print(m.group(0))                            # 返回匹配成功的整个子串\n",
    "print(m.span(0) )                            # 返回匹配成功的整个子串的索引\n",
    "print(m.group(1))                            # 返回第一个分组匹配成功的子串\n",
    "print(m.span(1) )                            # 返回第一个分组匹配成功的子串的索引\n",
    "print(m.group(2))                            # 返回第二个分组匹配成功的子串\n",
    "print(m.span(2) )                            # 返回第二个分组匹配成功的子串\n",
    "print(m.groups())                            # 等价于 (m.group(1), m.group(2), ...)\n",
    "print(m.group(3))                            # 不存在第三个分组"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## re.findall 方法\n",
    "\n",
    "在字符串中找到正则表达式所匹配的所有子串，并返回一个列表，如果没有找到匹配的，则返回空列表。\n",
    "\n",
    "注意： match 和 search 是匹配一次 findall 匹配所有。\n",
    "\n",
    "语法格式为：\n",
    "\n",
    "```\n",
    "findall(string[, pos[, endpos]])\n",
    "```\n",
    "参数：\n",
    "\n",
    "- `string` : 待匹配的字符串。\n",
    "- `pos `: 可选参数，指定字符串的起始位置，默认为 0。\n",
    "- `endpos` : 可选参数，指定字符串的结束位置，默认为字符串的长度。\n",
    "\n",
    "查找字符串中的所有数字："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['123', '456']\n",
      "['88', '12']\n"
     ]
    }
   ],
   "source": [
    "# -*- coding:UTF8 -*-\n",
    " \n",
    "import re\n",
    " \n",
    "pattern = re.compile(r'\\d+')   # 查找数字\n",
    "result1 = pattern.findall('runoob 123 google 456')\n",
    "result2 = pattern.findall('run88oob123google456', 0, 10)  # 注意左闭右开\n",
    " \n",
    "print(result1)\n",
    "print(result2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## re.finditer 方法\n",
    "\n",
    "和 `findall` 类似，在字符串中找到正则表达式所匹配的所有子串，并把它们作为一个迭代器返回。\n",
    "\n",
    "```\n",
    "re.finditer(pattern, string, flags=0)\n",
    "```\n",
    "\n",
    "|参数|\t描述|\n",
    "|----|------|\n",
    "|pattern|\t匹配的正则表达式|\n",
    "|string|\t要匹配的字符串。|\n",
    "|flags|\t标志位，用于控制正则表达式的匹配方式，如：是否区分大小写，多行匹配等等。|\n",
    "\n",
    "参见：正则表达式修饰符 - 可选标志"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "12\n",
      "32\n",
      "43\n",
      "3\n"
     ]
    }
   ],
   "source": [
    "# -*- coding: UTF-8 -*-\n",
    " \n",
    "import re\n",
    " \n",
    "it = re.finditer(r\"\\d+\",\"12a32bc43jf3\") \n",
    "for match in it: \n",
    "    print (match.group() )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## re.split 方法\n",
    "\n",
    "split 方法按照能够匹配的子串将字符串分割后返回列表，它的使用形式如下：\n",
    "\n",
    "```\n",
    "re.split(pattern, string[, maxsplit=0, flags=0])\n",
    "\n",
    "```\n",
    "\n",
    "|参数|\t描述|\n",
    "|----|------|\n",
    "|pattern|\t匹配的正则表达式|\n",
    "|string|\t要匹配的字符串。|\n",
    "|maxsplit|\t分隔次数，maxsplit=1 分隔一次，默认为 0，不限制次数。|\n",
    "|flags|\t标志位，用于控制正则表达式的匹配方式，如：是否区分大小写，多行匹配等等。|\n",
    "\n",
    "参见：正则表达式修饰符 - 可选标志"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['runoob', 'runoob', 'runoob', '']"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import re\n",
    "re.split('\\W+', 'runoob, runoob, runoob.')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['', ' ', 'runoob', ', ', 'runoob', ', ', 'runoob', '.', '']"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "re.split('(\\W+)', ' runoob, runoob, runoob.') "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['', 'runoob, runoob, runoob.']"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "re.split('\\W+', ' runoob, runoob, runoob.', 1) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['', 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '']"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "re.split('a*', 'hello world')   # 对于一个找不到匹配的字符串而言，split 不会对其作出分割"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 正则表达式修饰符 - 可选标志\n",
    "\n",
    "正则表达式可以包含一些可选标志修饰符来控制匹配的模式。修饰符被指定为一个可选的标志。多个标志可以通过按位 OR(|) 它们来指定。如 re.I | re.M 被设置成 I 和 M 标志：\n",
    "\n",
    "https://www.runoob.com/python/python-reg-expressions.html#flags"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 目录\n",
    "\n",
    "- [1. 基本匹配](#1.-基本匹配)\n",
    "- 2. 元字符\n",
    "    - 2.1 点运算符 .\n",
    "    - 2.2 字符集\n",
    "        - [2.2.1 否定字符集](#2.2.1-否定字符集)\n",
    "    - 2.3 重复次数\n",
    "        - 2.3.1 * 号\n",
    "        - [2.3.2 + 号](#2.3.2-+-号)\n",
    "        - 2.3.3 ? 号\n",
    "    - 2.4 {} 号\n",
    "    - 2.5 (...) 特征标群\n",
    "    - 2.6 | 或运算符\n",
    "    - 2.7 转码特殊字符\n",
    "    - 2.8 锚点\n",
    "        - 2.8.1 ^ 号\n",
    "        - 2.8.2 $ 号\n",
    "- 3. 简写字符集\n",
    "- 4. 零宽度断言(前后预查)\n",
    "    - 4.1 ?=... 正先行断言\n",
    "    - 4.2 ?!... 负先行断言\n",
    "    - 4.3 ?<= ... 正后发断言\n",
    "    - 4.4 ?<!... 负后发断言\n",
    "- 5. 标志\n",
    "    - 5.1 忽略大小写（Case Insensitive）\n",
    "    - 5.2 全局搜索（Global search）\n",
    "    - 5.3 多行修饰符（Multiline）"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. 基本匹配\n",
    "\n",
    "正则表达式其实就是在执行搜索时的格式，它由一些字母和数字组合而成。 例如：一个正则表达式 the，它表示一个规则：由字母t开始，接着是h，再接着是e。\n",
    "\n",
    "\"the\" => The fat cat sat on **the** mat.\n",
    "\n",
    "[在线练习](https://regex101.com/r/dmRygT/1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(20, 23)\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "print(re.search('the', ' The fat cat sat on the mat.').span())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "正则表达式`123`匹配字符串`123`。它逐个字符的与输入的正则表达式做比较。\n",
    "\n",
    "正则表达式是大小写敏感的，所以`The`不会匹配`the`。\n",
    "\n",
    "[在线练习](https://regex101.com/r/1paXsy/1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(1, 4)\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "print(re.search('The', ' The fat cat sat on the mat.').span())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2.元字符\n",
    "正则表达式主要依赖于元字符。 元字符不代表他们本身的字面意思，他们都有特殊的含义。一些元字符写在方括号中的时候有一些特殊的意思。以下是一些元字符的介绍：\n",
    "\n",
    "|元字符|\t描述|\n",
    "|----|----|\n",
    "|.|\t句号匹配任意单个字符除了换行符。|\n",
    "|[ ]|\t字符种类。匹配方括号内的任意字符。|\n",
    "|[^ ]|\t否定的字符种类。匹配除了方括号里的任意字符|\n",
    "|*|\t匹配>=0个重复的在*号之前的字符。|\n",
    "|+|\t匹配>=1个重复的+号前的字符。|\n",
    "|?|\t标记?之前的字符为可选.|\n",
    "|{n,m}|\t匹配num个大括号之间的字符 (n <= num <= m).|\n",
    "|(xyz)|\t字符集，匹配与 xyz 完全相等的字符串.|\n",
    "|\\||\t或运算符，匹配符号前或后的字符.|\n",
    "|\\\\|\t转义字符,用于匹配一些保留的字符 [ ] ( ) { } . * + ? ^ $ \\ ||\n",
    "|^|\t从开始行开始匹配.|\n",
    "|\\$|\t从末端开始匹配.|\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.1 点运算符`.`\n",
    "\n",
    "`.`是元字符中最简单的例子。 `.`匹配任意单个字符，但不匹配换行符。 例如，表达式`.ar`匹配一个任意字符后面跟着是`a`和`r`的字符串。\n",
    "\n",
    "\".ar\" => The **car** **par**ked in the **gar**age.\n",
    "\n",
    "[在线练习](https://regex101.com/r/xc9GkU/1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['car', 'par', 'gar']\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "pattern = re.compile(r'.ar')                    # 用于匹配至少一个数字\n",
    "m = pattern.findall('The car parked in the garage.')        # 查找头部，没有匹配\n",
    "print(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.2 字符集\n",
    "\n",
    "字符集也叫做字符类。 方括号用来指定一个字符集。 在方括号中使用连字符来指定字符集的范围。 在方括号中的字符集不关心顺序。 例如，表达式`[Tt]he` 匹配 `the` 和 `The`。\n",
    "\n",
    "\"[Tt]he\" => **The** car parked in **the** garage."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['The', 'the']\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "pattern = re.compile(r'[Tt]he')                    # 用于匹配至少一个数字\n",
    "m = pattern.findall('The car parked in the garage.')        # 查找头部，没有匹配\n",
    "print(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "方括号的句号就表示句号。 表达式 `ar[.]` 匹配 `ar.`字符串\n",
    "\n",
    "\"ar[.]\" => A garage is a good place to park a c**ar.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['ar.']\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "pattern = re.compile(r'ar[.]')                    # 用于匹配至少一个数字\n",
    "m = pattern.findall('A garage is a good place to park a car.')        # 查找头部，没有匹配\n",
    "print(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.2.1 否定字符集\n",
    "一般来说 `^` 表示一个字符串的开头，但它用在一个方括号的开头的时候，它表示这个字符集是否定的。 例如，表达式`[^c]ar` 匹配一个后面跟着ar的除了c的任意字符。\n",
    "\n",
    "\"[^c]ar\" => The car **par**ked in the **gar**age."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['par', 'gar']\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "pattern = re.compile(r'[^c]ar')                    # 用于匹配至少一个数字\n",
    "m = pattern.findall('The car parked in the garage.')        # 查找头部，没有匹配\n",
    "print(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2.3 重复次数\n",
    "\n",
    "后面跟着元字符 `+` `，` `*` `or` `?` 的，用来指定匹配子模式的次数。 这些元字符在不同的情况下有着不同的意思。\n",
    "\n",
    "### 2.3.1 `*` 号\n",
    "\n",
    "`*`号匹配 在`*`之前的字符出现大于等于0次。 例如，表达式 `a*` 匹配0或更多个以a开头的字符。表达式`[a-z]*` 匹配一个行中所有以小写字母开头的字符串。\n",
    "\n",
    "\"[a-z]\\*\" => T**he car parked in the garage** #21."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['', 'he', '', 'car', '', 'parked', '', 'in', '', 'the', '', 'garage', '', '']\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "pattern = re.compile(r'[a-z]*')                    # 用于匹配至少一个数字\n",
    "m = pattern.findall('The car parked in the garage.')        # 查找头部，没有匹配\n",
    "print(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`*`字符和`.`字符搭配可以匹配所有的字符`.*`。 `*`和表示匹配空格的符号`\\s`连起来用，如表达式`\\s*cat\\s*`匹配0或更多个空格开头和0或更多个空格结尾的cat字符串。\n",
    "\n",
    "\"\\s\\*cat\\s\\*\" => The fat **cat** sat on the con**cat**enation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[' cat ', 'cat']\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "pattern = re.compile(r'\\s*cat\\s*')                    # 用于匹配至少一个数字\n",
    "m = pattern.findall('The fat cat sat on the concatenation.')        # 查找头部，没有匹配\n",
    "print(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.3.2 + 号\n",
    "\n",
    "`+`号匹配`+`号之前的字符出现 `>=1` 次。 例如表达式`c.+t` 匹配以首字母c开头以t结尾，中间跟着至少一个字符的字符串。\n",
    "\n",
    "\"c.+t\" => The fat **cat sat on the mat**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['cat sat on the concatenation.']\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "pattern = re.compile(r'c.+t*')                    # 用于匹配至少一个数字\n",
    "m = pattern.findall('The fat cat sat on the concatenation.')        # 查找头部，没有匹配\n",
    "print(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.3.3 ? 号\n",
    "\n",
    "在正则表达式中元字符 `?` 标记在符号前面的字符为可选，即出现 0 或 1 次。 例如，表达式 `[T]?he` 匹配字符串 he 和 The。\n",
    "\n",
    "\"[T]he\" => **The** car is parked in the garage.\n",
    "\n",
    "\"[T]?he\" => **The** car is parked in t**he** garage."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['The']\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "pattern = re.compile(r'[T]he')                    # 用于匹配至少一个数字\n",
    "m = pattern.findall('The fat cat sat on the concatenation.')        # 查找头部，没有匹配\n",
    "print(m)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['The', 'he']\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "pattern = re.compile(r'[T]?he')                    # 用于匹配至少一个数字\n",
    "m = pattern.findall('The fat cat sat on the concatenation.')        # 查找头部，没有匹配\n",
    "print(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.4 {} 号\n",
    "\n",
    "在正则表达式中 `{}` 是一个量词，常用来一个或一组字符可以重复出现的次数。 例如， 表达式 `[0-9]{2,3}` 匹配最少 2 位最多 3 位 0~9 的数字。\n",
    "\n",
    "\"[0-9]{2,3}\" => The number was 9.**999**7 but we rounded it off to **10**.0."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {
    "height": "calc(100% - 180px)",
    "left": "10px",
    "top": "150px",
    "width": "273.188px"
   },
   "toc_section_display": true,
   "toc_window_display": true
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
